using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input.Touch;
using SPGE.TextureEffects;

namespace SPGE
{
    public abstract class GameItem : IGameItem
    {
        /// <summary>
        /// Sets the SpriteUse which will indicate if the SpriteBatch passed during Draw
        /// will be ready for drawing or not. Much better performance is achieved with 
        /// SpriteBatchUse.AlreadyBeganSpriteBatch but this does not allow for transformations
        /// to take place (which need the access to the SpriteBatch.Begin(...) method).
        /// </summary>
        public SpriteBatchUse SpriteUse
        { get; set; }

        private ITextureEffect effect;
        public ITextureEffect Effect
        {
            get { return effect; }
            set
            {
                if (effect != null)
                    effect.Dispose();

                this.OnLoadEnd += value.GameItemLoaded;
                effect = value;
            }
        }

        public Action<IGameItem> OnLoadStart
        { get; set; }
        public Action<IGameItem> OnLoadEnd
        { get; set; }
        public Action<IGameItem> OnRenderStart
        { get; set; }
        public Action<IGameItem> OnRenderEnd
        { get; set; }
        public Action<IGameItem> OnUpdateStart
        { get; set; }
        public Action<IGameItem> OnUpdateEnd
        { get; set; }

        /// <summary>
        /// A float to be used when rendering this GameItem. A greater RenderLevel means
        /// the GameItem will be drawn below the items with a smaller RenderLevel
        /// </summary>
        public float RenderLevel
        { get; set; }

        /// <summary>
        /// Path of item to be drawn
        /// </summary>
        public abstract string Path
        { get; protected set; }

        public Texture2D Texture
        { get; protected set; }

        public virtual Rectangle SourceRectangle
        { get; set; }

        private Rectangle? disabledRectangle = null;
        public virtual Rectangle DisabledSourceRectangle
        {
            get { return disabledRectangle ?? SourceRectangle; }
            set
            {
                disabledRectangle = value;
            }
        }

        public virtual float Opacity
        { get; set; }

        public Vector2 Position
        { get; set; }
        public Vector2 Size
        { get; set; }
        public Vector2 Scaling
        { get; set; }
    
        public float Rotation
        { get; set; }
        public Vector2 Origin
        { get; set; }

        public bool IsActive
        { get; set; }
        public bool IsVisible
        { get; set; }

        public virtual Rectangle ContainedArea
        {
            get
            {
                Vector2 tl = Position - (Origin * Size);

                return new Rectangle((int)tl.X, (int)tl.Y, (int)Size.X, (int)Size.Y);
            }
        }

        public virtual float UpdateOrder
        { get { return RenderLevel; } }

        #region Ctors

        public GameItem()
            : this(SpriteBatchUse.AlreadyBeganSpriteBatch)
        { }

        public GameItem(SpriteBatchUse spriteUse)
            : this(spriteUse, 0.1f)
        { }

        public GameItem(float renderLevel)
            : this(SpriteBatchUse.AlreadyBeganSpriteBatch, renderLevel)
        { }

        public GameItem(SpriteBatchUse spriteUse, float renderLevel)
        {
            SpriteUse = spriteUse;
            RenderLevel = renderLevel;
            Opacity = 1f;
            Rotation = 0f;
            Origin = new Vector2(0, 0);
            Position = new Vector2(0, 0);
            Scaling = new Vector2(1f);

            IsActive = true;
            IsVisible = true;
        }

        #endregion

        public abstract void Initialize();

        public virtual void Load(ResourceGroupHandler group)
        {
            if (OnLoadStart != null)
                OnLoadStart(this);

            Texture = group.LoadItem<Texture2D>(Path);
            if (Size == Vector2.Zero)
                Size = new Vector2(Texture.Width, Texture.Height);

            if (SourceRectangle == Rectangle.Empty)
                SourceRectangle = new Rectangle(0, 0, Texture.Width, Texture.Height);

            if (OnLoadEnd != null)
                OnLoadEnd(this);

        }

        /// <summary>
        /// Usefull only for custom items created dynamically like RenderTargets or 3D primitive models not loaded from ResourceGroup
        /// </summary>
        public virtual void Unload()
        { }

        public virtual void Draw(GameTime gameTime, SpriteBatch spriteBatch)
        {
            if (SpriteUse == SpriteBatchUse.EndedSpriteBatch)
                BeginSpriteBatch(spriteBatch, gameTime);

            if (OnRenderStart != null)
                OnRenderStart(this);

            #region Apply the one effect if it exists and is visible.
            var sourceRectangle = IsActive ? SourceRectangle : DisabledSourceRectangle;

            var texture = Texture;
            if (effect != null && effect.IsVisible)
            {
                texture = Effect.ApplyEffect(this);
                sourceRectangle = texture.Bounds;
            }
            #endregion

            if (SpriteUse == SpriteBatchUse.AlreadyBeganSpriteBatch)
                //This is a bit strange, but the origin takes into account the original size of the asset that is going to be drawn, and not
                // the resulting rectungle. I consider this a bug on behalf of XNA.
                spriteBatch.Draw(texture, new Rectangle((int)Position.X, (int)Position.Y, (int)(Size.X * Scaling.X), (int)(Size.Y * Scaling.Y)), sourceRectangle, new Color(Opacity, Opacity, Opacity, Opacity), Rotation, Origin * new Vector2(SourceRectangle.Width, SourceRectangle.Height), SpriteEffects.None, RenderLevel);
            else //Is assumed that positioning is done via effects, otherwise there is no need for the expense on SpriteBatch
                spriteBatch.Draw(texture, Vector2.Zero, sourceRectangle, new Color(1f, 1f, 1f, Opacity), Rotation, Origin * Size, Vector2.One, SpriteEffects.None, RenderLevel);
                

            if (OnRenderEnd != null)
                OnRenderEnd(this);

            if (SpriteUse == SpriteBatchUse.EndedSpriteBatch)
                spriteBatch.End();
        }

        protected virtual void BeginSpriteBatch(SpriteBatch sb, GameTime gt)
        {
#if DEBUG
            throw new Exception("You have to override BeginSpriteBatch method in GameItems with SpriteUse == SpriteBatchUse.EndedSpriteBatch");
#else
            sb.Begin();
#endif
        }
    }
}
